use crate::{
    error::{new_err, AppErr},
    web::cfg::CBOR_REQUEST_MAX_SIZE,
};
use actix_web::{
    body::BoxBody,
    dev::Payload,
    http::header::ContentType,
    web::{BufMut, BytesMut},
    FromRequest, HttpRequest, HttpResponse, Responder,
};
use futures_util::StreamExt;
use serde::{de::DeserializeOwned, Deserialize, Serialize};
use std::{
    future::Future,
    marker::PhantomData,
    ops::Deref,
    pin::Pin,
    task::{ready, Context, Poll},
};

use super::super::cfg::{HTTP_RESP_HEAD, HTTP_RESP_OK};

#[derive(Debug, Deserialize, Serialize)]
pub struct Cbor<T>(pub T);

impl<T> Deref for Cbor<T> {
    type Target = T;

    fn deref(&self) -> &Self::Target {
        &self.0
    }
}

impl<T: DeserializeOwned + Unpin> FromRequest for Cbor<T> {
    type Error = AppErr;
    type Future = CborFut<T>;

    fn from_request(_req: &HttpRequest, payload: &mut Payload) -> Self::Future {
        let payload = payload.take();
        CborFut {
            payload,
            buf: BytesMut::new(),
            _d: PhantomData,
        }
    }
}

pub struct CborFut<T> {
    payload: Payload,
    buf: BytesMut,
    _d: PhantomData<T>,
}

impl<T: DeserializeOwned + Unpin> CborFut<T> {
    fn parse(&self) -> Result<Cbor<T>, AppErr> {
        let value: T = serde_cbor::from_slice(&self.buf)?;
        Ok(Cbor(value))
    }
}

impl<T: DeserializeOwned + Unpin> Future for CborFut<T> {
    type Output = Result<Cbor<T>, AppErr>;

    fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
        let this = self.get_mut();
        loop {
            let item = ready!(this.payload.poll_next_unpin(cx));
            match item {
                None => {
                    return Poll::Ready(this.parse());
                }

                Some(Ok(item)) => {
                    if item.len() + this.buf.len() >= CBOR_REQUEST_MAX_SIZE {
                        return Poll::Ready(Err(new_err("cbor payload large")));
                    }
                    this.buf.put_slice(&item);
                }

                Some(Err(_)) => {
                    return Poll::Ready(Err(new_err("payload err")));
                }
            };
        }
    }
}

impl<T: Serialize> Responder for Cbor<T> {
    type Body = BoxBody;

    fn respond_to(self, _req: &HttpRequest) -> HttpResponse {
        let body = serde_cbor::to_vec(&self.0).unwrap();
        HttpResponse::Ok()
            .content_type(ContentType::octet_stream())
            .insert_header((HTTP_RESP_HEAD, HTTP_RESP_OK))
            .body(body)
    }
}

pub fn new_ok_cbor<T>(value: T) -> Result<Cbor<T>, AppErr> {
    Ok(Cbor(value))
}

pub type CborRet<T> = Result<Cbor<T>, AppErr>;
